AstVerb.php

<?php

namespace Tlf\Scrawl\Ext\MdVerb;

class Ast {

    public \Tlf\Scrawl $scrawl;

    public function __construct($scrawl){
        $this->scrawl = $scrawl;
    }

    /**
     * Get an ast & optionally load a custom template for it
     *
     * @usage @ast(class.ClassName.methods.docblock.description, ast/default)
     */
    public function get_markdown($key, $template='ast/default'){
        $value = $this->get_ast($key);
        $template = $this->scrawl->get_template($template, [$key, $value, $this]);
        return $template;
    }

    /**
     * @param $key a dotproperty like `class.ClassName.methods.MethodName.docblock.description`
     * @param $length the number of dots in the dotproperty to traverse. -1 means all
     */
    public function get_ast(string $key, int $length=-1){
        $parts = explode('.',$key);
        if ($length!=-1){
            $parts = array_slice($parts, 0,$length);
        }
        $group = array_shift($parts);
        $class = array_shift($parts);
        $ast = $this->scrawl->get('ast', $group.'.'.$class);

        if ($ast==null){
            $this->scrawl->warn("Ast $group not found", "Can't load '$class'.");
            return;
        }

        // echo "\n\nAST:";
        // var_dump($ast);
        // var_dump($group);
        // var_dump($class);
        // echo "\n\n";

        $stack = $group.'.'.$class;
        $next = $ast;
        foreach ($parts as $p){
            $current = $next;
            $stack .= '.'.$p;

            // echo "\n\nStack: $stack";
            if (!isset($current[$p])&&is_array($current)){
                foreach ($current as $i=>$item){
                    if (!is_numeric($i))continue;
                    echo "\n\n".$p."::";
                    echo $item['name']."\n\n";
                    if ($item['name']==$p){
                        $next = $item;
                        continue 2;
                    }
                }
                // echo "Current: ";
                // print_r($current);
                $this->scrawl->warn("Ast Not Found", "Can't load '$key'. Failed at '$current.$p'");
                return null;
                break;
                echo "\n\nFailed at ".$stack;
                echo "\n\nsomething went wrong\n\n";
                exit;
            } else if (!isset($current[$p])){
                $this->scrawl->warn("Ast Not Found", "Can't load '$key'. Failed at '$p'");
                return null;
                echo "\n\ncan't get next item\n\n";
                exit;
            }

            $next = $current[$p];
        }

        // exit;
        return $next;
    }

    public function getVerbs(): array{
        return [
            'ast'=>'verbAst', //alias for @ast_class()
            'classMethods'=>'getClassMethodsTemplate',
            'ast_class'=> 'getAstClassInfo',
        ];
    }

    /**
     *
     * @param $fqn The fully qualified name, like a class name or function with its namespace
     * @param $dotProperty For class, something like 'methods.methodName.docblock' to get a docblock for the given class. 
     *
     * @example @ast(\Tlf\Scrawl\Ext\MdVerb\Ast, methods.getAstClassInfo.docblock)
     * @mdverb ast
     *
     */
    public function getAstClassInfo(array $info, string $fqn, string $dotProperty){
        // @ast(class,Phad\Test\Documentation,methods.testWriteAView.docblock)
        
        $class = $this->scrawl->getOutput('astClass', $fqn);

        // var_dump(array_keys($this->scrawl->getOutputs('astClass')));

        if ($class == 'null') return "class '$fqn' not found in ast.";
    


        $propStack = explode('.', $dotProperty);

        $head = $class;
        if (!is_array($head)){
            $file = $info['file']->path;
            // $this->scrawl->error('@ast or @ast_class in '.$file,'requires "astClass" output for fqn "'.$fqn.'" to be an array, but a '. gettype($class).' was returned.');
            $this->scrawl->error("@ast($fqn, $dotProperty) failed", 'in '.$file);
            return "@ast($fqn) failed";
        }
        foreach ($propStack as $prop){
            if ($prop=='*'){
                return print_r($head,true);
            }
            
            if (!isset($head[$prop])){
                $options =  [];
                foreach ($head as $key=>$value){
                    if (is_numeric($key) && ($value['name']??null)==$prop){
                        $head = $head[$key];
                        continue 2;
                    } else if (is_numeric($key) && isset($value['name'])){
                        $options[] = $value['name'];
                    }
                }
                $options = array_merge($options, array_keys($head));
                $msg = "Cannot find '$prop' part of '$dotProperty' on '$fqn'. You may try one of: ". implode(", ", $options);
                $this->scrawl->error('@ast or @ast_class', $msg);
                return $msg;
            }
            $head = $head[$prop];
        }

        if (is_array($head)){
            if (isset($head['body']))return $head['body'];
            else if (isset($head['description']))return $head['description'];
            else if (isset($head['src']))return $head['src'];
            $msg="Found an array for '$dotProperty' on '$fqn' with keys: ".implode(", ", array_keys($head));
            $this->scrawl->error('@ast or @ast_class', $msg);
            return $msg;
        }

        return $head;
    }

    /**
     *
     * @return string replacement
     */
    public function verbAst($info, $className, $dotProperty){

        //
        // This method not currently functional. Ugggh!!!
        //

        // $parts = explode('.', $classDotThing);
        // $class = array_shift($parts);
        // return $this->getAstClassInfo($info, $class, 'method.'.implode('.',$parts));
        return $this->getAstClassInfo($info, $className, $dotProperty);
        // $key = $class;
        // var_dump($key);
        // $output = $this->scrawl->getOutput('api',$key);
//
        // if (trim($output)=='')return "--ast '${class}' not found--";
//
        // return $output;
    }

    public function getClassMethodsTemplate($verb, $argListStr, $line){
        if ($verb!='classMethods')return;

        $args = explode(',', $argListStr);

        $className = $args[0];
        $visibility = '*';
        if (isset($args[1])){
            $visibility = trim($args[1]);
        }
        
        $class = $this->scrawl->getOutput('astClass', $className);

        $template = dirname(__DIR__,2).'/Template/classMethods.md.php';
        ob_start();
        require($template);
        $final = ob_get_clean();
        return $final;
    }

}